package com.example.accountprovider.tcc.impl;

import com.example.accountprovider.domain.Account;
import com.example.accountprovider.domain.TccTransaction;
import com.example.accountprovider.mapper.AccountMapper;
import com.example.accountprovider.mapper.TccTransactionMapper;
import com.example.accountprovider.tcc.IAccountTccAction;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.example.constants.TccState;
import org.example.exception.BizException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;

/**
 * try-confirm/cancel
 * @author Kong
 * @date 2021-9-14 15:15
 */
@Slf4j
@Service
public class AccountTccAction implements IAccountTccAction {
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private TccTransactionMapper tccTransactionMapper;
    @Override
    public boolean prepareDecreaseAccount(BusinessActionContext businessActionContext, Integer userId, BigDecimal balance) {
        /**
         * 一阶段需要考虑两个问题：
         * 1、悬挂和空回滚
         * 2、正常执行时，告诉第二接口已经预留
         */
        if(userId == null || userId<=0 || balance == null || balance.compareTo(BigDecimal.ZERO ) <0) {
            log.error("参数传输不合法:userId=%d,balance=%d",userId,balance);
            throw new BizException("传输数据不合法");
        }
        // 查询账户余额，预留资源
        Account account = accountMapper.getAccountForUpdate(userId);
        if(account.getBalance().compareTo(balance)<0) {
            throw new BizException("账户资金不足，无法进行资金锁定");
        }
        log.info("全局事务xid:"+ RootContext.getXID());
        log.info(RootContext.getBranchType());
        try {
            // 冻结资金
            int row = accountMapper.updateFrozenAccount(userId,balance.doubleValue());
            if(row <1) {
                throw new BizException("冻结资金失败");
            }
            // 记录日志，为了防止空回滚、幂等、悬挂导致的数据不一致
            TccTransaction tccTransaction = new TccTransaction();

            tccTransaction.setBranchId(String.valueOf(businessActionContext.getBranchId()));
            // 全局事务
            tccTransaction.setXid(businessActionContext.getXid());
            tccTransaction.setGmtCreate(new Date());
            tccTransaction.setGmtUpdate(new Date());
            // 状态
            tccTransaction.setState(TccState.PREPARED.getValue());
            // 插入失败，说明已经有二阶段接口在执行
            tccTransactionMapper.insert(tccTransaction);
            return true;
        }catch (Exception e) {
            e.printStackTrace();
            throw new BizException("悬挂!一阶段冻结资金失败");
        }

    }

    @Override
    public boolean commitDecreaseAccount(BusinessActionContext businessActionContext) {
        /**
         * 二阶段接口
         * 1、不允许出现空提交，即TccTransaction不存在，抛出异常
         * 2、幂等，判断状态是否为初始化，如果是则继续执行，如果为已提交则直接返回true，否则抛出异常
         */
        String xid = businessActionContext.getXid();
        String branchId = String.valueOf(businessActionContext.getBranchId());
        Integer userId = Integer.valueOf(businessActionContext.getActionContext().get("userId").toString());
        Double balance = Double.valueOf(businessActionContext.getActionContext().get("balance").toString());
        // 查询记录的事务表
        TccTransaction tccTransaction = tccTransactionMapper.getByXidAndBranchIdForUpdate(xid,branchId);
        // try必须发生在commit，如果TccTransaction不存在，说明try没有执行，空提交，此时直接抛出异常
        if(tccTransaction == null) {
            throw new BizException("TCC业务执行异常!异常原因:空提交，一阶段执行异常");
        }
        // 如果状态为已提交，直接返回true，保证幂等性，原因是提交时由于超时的原因，可能回出现重试
        // 当有业务需求时，也可以直接返回true，比如规定7天冻结期之后才进行划账
        if(tccTransaction.getState().equals(TccState.COMMITTED.getValue())) {
            return true;
        } else if(tccTransaction.getState().equals(TccState.ROLLBACKED.getValue())) {
            throw new BizException("TCC业务执行异常!异常原因:空提交，已经回滚的事务不允许再次提交");
        }
        int row = accountMapper.updateFrozenToUsed(userId,balance);
        if(row <1 ) {
            throw new BizException("二阶段提交失败");
        }
        tccTransactionMapper.updateStateByXidAndBranchId(TccState.COMMITTED.getValue(),xid,branchId);
        return true;
    }

    @Override
    public boolean rollbackAccount(BusinessActionContext businessActionContext) {
        String xid = businessActionContext.getXid();
        String branchId = String.valueOf(businessActionContext.getBranchId());
        Integer userId = Integer.valueOf(businessActionContext.getActionContext().get("userId").toString());
        Double balance = Double.valueOf(businessActionContext.getActionContext().get("balance").toString());
        TccTransaction tccTransaction = tccTransactionMapper.getByXidAndBranchIdForUpdate(xid,branchId);
        // 防止空回滚或重复回滚
        if(tccTransaction == null ) {
            tccTransaction = new TccTransaction();
            // 分支事务
            tccTransaction.setBranchId(branchId);
            // 全局事务
            tccTransaction.setXid(xid);
            tccTransaction.setGmtCreate(new Date());
            tccTransaction.setGmtUpdate(new Date());
            // 状态
            tccTransaction.setState(TccState.ROLLBACKED.getValue());
            tccTransactionMapper.insert(tccTransaction);
            return true;
        } else if(tccTransaction.getState().equals(TccState.COMMITTED.getValue())) {
            //throw new BizException("已经提交的事务不允许回滚");
            log.warn("已经提交的事务不允许回滚");
        } else if(tccTransaction.getState().equals(TccState.ROLLBACKED.getValue())) {
            return true;
        }
        // 省略对冻结库存的判断
        int row = accountMapper.rollbackFrozen(userId,balance);
        if(row <1) {
            throw new BizException("全局事务xid="+xid+"回滚失败");
        }
        tccTransactionMapper.updateStateByXidAndBranchId(TccState.ROLLBACKED.getValue(),xid,branchId);
        return true;
    }
}
